/*
 * MIT License
 *
 * Copyright (c) 2019 Mr.css
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */

package cn.seaboot.commons.core;

import cn.seaboot.commons.exception.ServiceException;
import cn.seaboot.commons.lang.P;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.Calendar;
import java.util.Date;
import java.util.WeakHashMap;

/**
 * 日起格式化
 * <p>
 * Locale的配置全局有效，如果需要单独设置，需要自己另起函数。
 *
 * @author Mr.css
 * @version 2020-11-03 分离代码，java8的相关代码，全部转移到LocalDateUtils。（新系统完全弃用java.sql包的日期）
 */
public class DateUtils {
    private DateUtils() {
    }

    /**
     * long 类型转 date 格式字符串
     *
     * @param l long value
     * @return date
     */
    public static String formatDate(long l) {
        return LocalDateUtils.formatDate(l);
    }

    /**
     * java.util.Date类型转 date 格式字符串
     *
     * @param date Date value
     * @return date
     */
    public static String formatDate(Date date) {
        return LocalDateUtils.formatDate(date);
    }

    /**
     * long 类型转 time 格式字符串
     *
     * @param l long value
     * @return date
     */
    public static String formatTime(long l) {
        return formatTime(new Date(l));
    }

    /**
     * java.util.Date类型转 time 格式字符串
     *
     * @param date Date value
     * @return date
     */
    public static String formatTime(Date date) {
        return LocalDateUtils.formatTime(date);
    }

    /**
     * long 类型转 datetime 格式字符串
     *
     * @param l long value
     * @return date
     */
    public static String formatDateTime(long l) {
        return LocalDateUtils.formatDateTime(l);
    }

    /**
     * java.util.Date类型转 datetime 格式字符串
     *
     * @param date Date value
     * @return date
     */
    public static String formatDateTime(Date date) {
        return LocalDateUtils.formatDateTime(date);
    }

    /**
     * long 类型转紧凑型date格式字符串
     *
     * @param l long value
     * @return date
     */
    public static String formatCompactDate(long l) {
        return LocalDateUtils.formatCompactDate(l);
    }

    /**
     * java.util.Date类型转紧凑型date格式字符串
     *
     * @param date Date value
     * @return date
     */
    public static String formatCompactDate(Date date) {
        return LocalDateUtils.formatCompactDate(date);
    }

    /**
     * long 类型转紧凑型 time 格式字符串
     *
     * @param l long value
     * @return date
     */
    public static String formatCompactTime(long l) {
        return LocalDateUtils.formatCompactTime(l);
    }

    /**
     * java.util.Date类型转紧凑型 time 格式字符串
     *
     * @param date Date value
     * @return date
     */
    public static String formatCompactTime(Date date) {
        return LocalDateUtils.formatCompactTime(date);
    }

    /**
     * long 类型转紧凑型 datetime 格式字符串
     *
     * @param l long value
     * @return date
     */
    public static String formatCompactDateTime(long l) {
        return LocalDateUtils.formatCompactDateTime(l);
    }

    /**
     * java.util.Date类型转紧凑型 datetime 格式字符串
     *
     * @param date Date value
     * @return date
     */
    public static String formatCompactDateTime(Date date) {
        return LocalDateUtils.formatCompactDateTime(date);
    }

    /**
     * 日期加n天
     *
     * @param date     日期
     * @param deltaDay n
     * @return Calendar
     */
    public static Calendar addDay(Date date, int deltaDay) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        calendar.add(Calendar.DAY_OF_MONTH, deltaDay);
        return calendar;
    }

    /**
     * 日期加n秒
     *
     * @param date        日期
     * @param deltaSecond n
     * @return Calendar
     */
    public static Calendar addSecond(Date date, int deltaSecond) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        calendar.add(Calendar.SECOND, deltaSecond);
        return calendar;
    }

    /**
     * 日期加n//月/天...
     *
     * @param date  日期
     * @param field 可选字段：Calendar.YEAR,Calendar.MONTH,Calendar.WEEK_OF_YEAR,Calendar.DAY_OF_MONTH,
     *              Calendar.HOUR_OF_DAY,Calendar.MINUTE,Calendar.SECOND,Calendar.MILLISECOND。
     * @param delta n
     * @return Calendar
     */
    public static Calendar add(Date date, int field, int delta) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        calendar.add(field, delta);
        return calendar;
    }

    /**
     * 计算一个月最后一天
     *
     * @param date 日期
     * @return Calendar
     */
    public static Calendar getLastDayOfMonth(Date date) {
        Calendar calendar = Calendar.getInstance();
        if (date != null) {
            calendar.setTime(date);
        }
        calendar.set(Calendar.DAY_OF_MONTH, calendar.getActualMaximum(Calendar.DAY_OF_MONTH));
        return calendar;
    }

    /**
     * 获取当前 datetime 紧凑型字符串
     *
     * @param format format
     * @return datetime
     */
    public static String getCurDateTime(String format) {
        return format(new Date(), format);
    }

    /**
     * date 字符串转日期
     *
     * @param str a string
     * @return Date
     */
    public static Date parseDate(String str) {
        return parseDate(LocalDateUtils.parseDate(str));
    }

    /**
     * time 字符串转日期
     *
     * @param str a string
     * @return Date
     */
    public static Date parseTime(String str) {
        return parseDate(LocalDateUtils.parseTime(str));
    }

    /**
     * datetime 字符串转日期
     *
     * @param str a string
     * @return Date
     */
    public static Date parseDateTime(String str) {
        return parseDate(LocalDateUtils.parseDateTime(str));
    }

    /**
     * 紧凑型 date 字符串转日期
     *
     * @param str a string
     * @return Date
     */
    public static Date parseCompactDate(String str) {
        return parseDate(LocalDateUtils.parseCompactDate(str));
    }

    /**
     * 紧凑型 time 字符串转日期
     *
     * @param str a string
     * @return Date
     */
    public static Date parseCompactTime(String str) {
        return parseDate(LocalDateUtils.parseCompactTime(str));
    }

    /**
     * 紧凑型 datetime 字符串转日期
     *
     * @param str a string
     * @return Date
     */
    public static Date parseCompactDateTime(String str) {
        return parseDate(LocalDateUtils.parseCompactDateTime(str));
    }

    /**
     * LocalDateTime 类型转 Date
     *
     * @param date LocalDateTime
     * @return Date
     */
    public static Date parseDate(LocalDateTime date) {
        return Date.from(date.atZone(P.zoneId()).toInstant());
    }

    /**
     * LocalDate 类型转 Date
     *
     * @param date LocalDate
     * @return Date
     */
    public static Date parseDate(LocalDate date) {
        return Date.from(LocalDateTime.of(date, LocalTime.MIN).atZone(P.zoneId()).toInstant());
    }

    /**
     * LocalTime 类型转 Date
     *
     * @param date LocalTime
     * @return Date
     */
    public static Date parseDate(LocalTime date) {
        return Date.from(LocalDateTime.of(LocalDate.MIN, date).atZone(P.zoneId()).toInstant());
    }

    /**
     * Object -> java.util.Date
     *
     * @param obj Object
     * @return String
     */
    public static Date parseDate(Object obj) {
        if (obj == null) {
            return null;
        } else if (obj instanceof Date) {
            return (Date) obj;
        } else if (obj instanceof Long) {
            return new Date((Long) obj);
        } else if (obj instanceof String) {
            return java.sql.Date.valueOf((String) obj);
        } else if (obj instanceof LocalDateTime) {
            return parseDate((LocalDateTime) obj);
        } else if (obj instanceof LocalTime) {
            return parseDate((LocalTime) obj);
        } else if (obj instanceof LocalDate) {
            return parseDate((LocalDate) obj);
        }
        throw new ClassCastException(obj + " to Date class case failed!");
    }

    /**
     * 获取时间的季节
     *
     * @return 季节(0春 / 1夏 / 2秋 / 3冬)
     */
    public int getSeason(Date date) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        return calendar.get(Calendar.DAY_OF_MONTH) / 3;
    }

    /**
     * long 类型转字符串
     *
     * @param l       long value
     * @param pattern pattern
     * @return date
     */
    public static String format(long l, String pattern) {
        return format(new Date(l), pattern);
    }

    /**
     * java.util.Date类型转字符串
     *
     * @param date    Date value
     * @param pattern pattern
     * @return date
     */
    public static String format(Date date, String pattern) {
        return DateFormatHolder.formatFor(pattern).format(date);
    }

    /**
     * 字符串转日期
     *
     * @param str     a string
     * @param pattern pattern
     * @return Date
     */
    public static Date parse(String str, String pattern) {
        try {
            return DateFormatHolder.formatFor(pattern).parse(str);
        } catch (ParseException e) {
            throw new ServiceException("date format failed: ", e);
        }
    }

    /**
     * ThreadLocal will remove when thread dead, but, when using thread pool, the thread maybe never dead before system exit,
     * in this way, the threadLocal also never remove.
     * Usually it doesn't cause bugs, because the SimpleDateFormat always used in one thread. if necessary, you can clear the threadLocal.
     */
    public static void clearThreadLocal() {
        DateFormatHolder.clearThreadLocal();
    }

    /**
     * DateFormat 缓存
     * <p>
     * A SimpleDateFormat Holder, use {@link ThreadLocal} and {@link WeakHashMap}
     */
    static final class DateFormatHolder {
        private static final ThreadLocal<WeakHashMap<String, SimpleDateFormat>> LOCAL_FORMATS =
                ThreadLocal.withInitial(WeakHashMap::new);

        static SimpleDateFormat formatFor(String pattern) {

            // soft reference map
            WeakHashMap<String, SimpleDateFormat> map = LOCAL_FORMATS.get();

            // create dateformat if undefined
            SimpleDateFormat format = map.get(pattern);
            if (format == null) {
                format = new SimpleDateFormat(pattern, P.locale());
                map.put(pattern, format);
            }

            return format;
        }

        static void clearThreadLocal() {
            LOCAL_FORMATS.remove();
        }
    }
}
